/*
 * Copyright (C) 2006 Princeton Softech, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.princetonsoftech.maven.psteclipse;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
import org.apache.maven.artifact.installer.ArtifactInstallationException;
import org.apache.maven.artifact.installer.ArtifactInstaller;
import org.apache.maven.artifact.repository.DefaultArtifactRepository;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;

/**
 * <ul>
 * <li>Title: DeployMojo</li>
 * <li>Description: The class <code>DeployMojo</code> is a Maven Mojo that
 * "scrapes" an Eclipse home directory and creates a package and POM for each
 * plugin for deployment to a Maven repository.</li>
 * <li>Created: Aug 1, 2006 by: prippete01</li>
 * </ul>
 * 
 * @author $Author: prippete01 $
 * @version $Revision: 1.10 $
 * @goal deploy
 * @phase deploy
 * @requiresProject false
 */
public class DeployMojo extends AbstractEclipseMojo implements DeployConstants,
		ManifestConstants {

	/** @parameter default-value="${localRepository}" */
	private org.apache.maven.artifact.repository.ArtifactRepository localRepository;

	/** @component */
	private ArtifactInstaller installer;

	/**
	 * The poms.
	 */
	private final List poms = new ArrayList();

	/**
	 * The artifacts.
	 */
	private final HashMap artifacts = new HashMap();

	/**
	 * The prefixes.
	 */
	private final List prefixes = new ArrayList();

	/**
	 * The eclipse home.
	 * 
	 * @parameter expression="${eclipseHome}"
	 */
	private File eclipseHome;

	/**
	 * The repository home.
	 * 
	 * @parameter expression="${repositoryHome}"
	 */
	private File repositoryHome;

	/**
	 * The group id.
	 * 
	 * @parameter expression="${groupId}"
	 */
	private final String groupId = "psteclipse";

	/**
	 * The prefix.
	 * 
	 * @parameter expression="${prefix}"
	 */
	private String prefix;

	/**
	 * Constructs a new <code>DeployMojo</code> instance.
	 */
	public DeployMojo() {
		super();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.princetonsoftech.maven.psteclipse.AbstractEclipseMojo#doExecute()
	 */
	@Override
	protected void doExecute() throws MojoExecutionException,
			MojoFailureException {
		if (eclipseHome == null) {
			eclipseHome = new File(System.getProperty("user.home")
					+ File.separator + "eclipse");
		}
		if (!eclipseHome.isDirectory()) {
			throw new MojoExecutionException("The 'eclipseHome' location '"
					+ eclipseHome + "' is not a valid directory");
		}
		if (repositoryHome == null) {
			repositoryHome = new File(System.getProperty("java.io.tmpdir"));
			getLog().info(
					"Defaulting 'repositoryHome' to '" + repositoryHome + "'");
		}
		if (!repositoryHome.isDirectory()) {
			throw new MojoExecutionException("The 'repositoryHome' location '"
					+ repositoryHome + "' is not a valid directory");
		}
		if (prefix == null) {
			getLog().info("Defaulting 'prefix' to '<none>'");
		}
		if (prefix != null) {
			final String[] temp = prefix.split(",");
			for (final String element : temp) {
				String prefix = element;
				if (!prefix.endsWith(".")) {
					prefix = prefix + ".";
				}
				prefixes.add(prefix);
			}
		}
		try {
			populateRepository();
		} catch (final IOException e) {
			throw new MojoExecutionException("Repository population failed", e);
		}
		try {
			writePOMs();
		} catch (final Exception e) {
			throw new MojoExecutionException("POM writing failed", e);
		}

	}

	/**
	 * Populates the repository.
	 * 
	 * @throws IOException
	 *             if an I/O error occurs.
	 */
	private void populateRepository() throws IOException {
		final File pluginHome = new File(eclipseHome, DIR_PLUGINS);
		if (!pluginHome.isDirectory()) {
			throw new FileNotFoundException(
					"The eclipse 'plugins' directory was not found");
		}
		getLog().info("Processing plugins in '" + pluginHome + "'");
		final File[] plugins = pluginHome.listFiles();
		for (final File plugin : plugins) {
			final String name = plugin.getName();
			boolean include;
			if (prefixes.size() > 0) {
				include = false;
				for (int j = 0; j < prefixes.size(); j++) {
					final String prefix = (String) prefixes.get(j);
					if (name.startsWith(prefix)) {
						include = true;
						break;
					}
				}
			} else {
				include = true;
			}
			if (!include) {
				getLog().debug("Skipping plug-in '" + name + "'");
				continue;
			}
			getLog().info("Processing plug-in '" + name + "'");
			if (plugin.isDirectory()) {
				handlePluginDirectory(plugin);
			} else {
				handlePluginFile(plugin);
			}
		}
	}

	/**
	 * Copies the plugin's jars and creates a POM for the plugin itself.
	 * 
	 * @param pluginDirectory
	 *            the plugin's directory.
	 * @throws IOException
	 *             if an I/O error occurs.
	 */
	private void handlePluginDirectory(final File pluginDirectory)
			throws IOException {
		final File manifestFile = new File(pluginDirectory,
				"META-INF/MANIFEST.MF");
		if (!manifestFile.isFile()) {
			getLog().error(
					"The directory plugin '" + pluginDirectory
							+ "' does not have a manifest");
			return;
		}
		final Manifest manifest = new Manifest(
				new FileInputStream(manifestFile));
		final ManifestParser parser = new ManifestParser(manifest);
		parser.setGroupId(groupId);
		parser.parse();
		if (!parser.hasArtifactId()) {
			getLog().error(
					"The plugin '" + pluginDirectory
							+ "' does not have a bundle symbolic name");
			return;
		}
		if (!parser.hasVersion()) {
			getLog().error(
					"The plugin '" + pluginDirectory
							+ "' does not have a version");
			return;
		}
		final Pom pom = buildPOM(parser, PACKAGING_POM);
		final Iterator iterator = parser.getDependencyManagement()
				.getDependencies().iterator();
		while (iterator.hasNext()) {
			final Dependency dependency = (Dependency) iterator.next();
			final String scope = dependency.getScope();
			if (SCOPE_SYSTEM.equals(scope)) {
				final File sourceFile = new File(pluginDirectory, dependency
						.getSystemPath());
				if (!sourceFile.isFile()) {
					getLog().warn(
							"The plugin '" + pluginDirectory
									+ "' declares a classpath entry '"
									+ dependency.getSystemPath()
									+ "' which does not exist");
					continue;
				}
				if (dependency.getVersion() == null) {
					dependency.setVersion(parser.getVersion());
				}
				final File destinationFile = new File(repositoryHome,
						dependency.getArtifactId() + "-"
								+ dependency.getVersion() + EXTENSION_JAR);
				copyFile(sourceFile, destinationFile);
			}
			pom.getDependencyManagement().addDependency(dependency);
		}
	}

	/**
	 * Copies the specified jar-based plugin to the repository directory.
	 * 
	 * @param pluginFile
	 *            the plugin's file.
	 * @throws IOException
	 *             if an I/O error occurs.
	 */
	private void handlePluginFile(final File pluginFile) throws IOException {
		if (pluginFile.getName().toLowerCase().endsWith("zip")
				|| pluginFile.getName().toLowerCase().endsWith("jar")) {
			final JarFile jarFile = new JarFile(pluginFile);
			final Manifest manifest = jarFile.getManifest();
			final ManifestParser parser = new ManifestParser(manifest);
			parser.setGroupId(groupId);
			parser.parse();
			if (!parser.hasArtifactId()) {
				getLog().error(
						"The plugin '" + pluginFile
								+ "' does not have a bundle symbolic name");
				return;
			}
			if (!parser.hasVersion()) {
				getLog().error(
						"The plugin '" + pluginFile
								+ "' does not have a version");
				return;
			}
			final Pom pom = buildPOM(parser, PACKAGING_JAR);
			final Iterator iterator = parser.getDependencyManagement()
					.getDependencies().iterator();
			while (iterator.hasNext()) {
				final Dependency dependency = (Dependency) iterator.next();
				final String scope = dependency.getScope();
				if (SCOPE_SYSTEM.equals(scope)) {
					getLog()
							.warn(
									"The plugin '"
											+ pluginFile
											+ "' declares a classpath entry '"
											+ dependency.getSystemPath()
											+ "' which cannot be resolved (it's a jar plugin)");
					continue;

				}
				pom.getDependencyManagement().addDependency(dependency);
			}
			final String artifactId = parser.getArtifactId();
			final File destinationFile = new File(repositoryHome, artifactId
					+ "-" + parser.getVersion() + EXTENSION_JAR);
			copyFile(pluginFile, destinationFile);
			pom.setFile(destinationFile);
		} else {
			getLog().warn(
					"skipped file, not an archive! " + pluginFile.getName());
		}
	}

	/**
	 * Builds and returns a new POM instance for the specified manifest parser
	 * and packaging.
	 * 
	 * @param parser
	 *            the manifest parser.
	 * @param packaging
	 *            the packaging.
	 * @return the POM.
	 */
	private Pom buildPOM(final ManifestParser parser, final String packaging) {
		final Pom pom = new Pom();
		pom.setParser(parser);
		final String artifactId = parser.getArtifactId();
		final String version = parser.getVersion();
		final File file = new File(repositoryHome, artifactId + "-" + version
				+ EXTENSION_POM);
		pom.setPomFile(file);
		String name = file.getName();
		final int index = name.lastIndexOf('.');
		name = name.substring(0, index) + EXTENSION_POM;

		pom.setPackaging(packaging);
		pom.setGroupId(groupId);
		pom.setArtifactId(artifactId);
		pom.setVersion(version);
		poms.add(pom);
		artifacts.put(pom.getArtifactId(), pom);
		return pom;
	}

	/**
	 * Writes the POMs for the plug-ins.
	 * 
	 * @throws IOException
	 *             if an I/O error occurs.
	 * @throws ArtifactInstallationException
	 */
	private void writePOMs() throws IOException, ArtifactInstallationException {
		final Iterator iterator = poms.iterator();
		while (iterator.hasNext()) {
			final Pom pom = (Pom) iterator.next();
			writePOM(pom);
			installPOMCommands(pom);
		}
	}

	/**
	 * Writes an XML POM from the specified POM instance.
	 * 
	 * @param pom
	 *            the POM.
	 * @throws IOException
	 *             if an I/O error occurs.
	 */
	private void writePOM(final Pom pom) throws IOException {
		final StringBuffer buffer = new StringBuffer();
		buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		buffer.append("<project>\n");
		buffer.append("   <modelVersion>4.0.0</modelVersion>\n");
		buffer.append("   <groupId>");
		buffer.append(pom.getGroupId());
		buffer.append("</groupId>\n");
		buffer.append("   <artifactId>");
		buffer.append(pom.getArtifactId());
		buffer.append("</artifactId>\n");
		buffer.append("   <version>");
		buffer.append(pom.getVersion());
		buffer.append("</version>\n");
		buffer.append("   <packaging>");
		buffer.append(pom.getPackaging());
		buffer.append("</packaging>\n");
		buffer.append("   <distributionManagement>\n");
		buffer.append("      <repository>\n");
		buffer.append("         <id>");
		buffer.append("a");
		buffer.append("</id>\n");
		buffer
				.append("         <name>PST Apollo Development Repository</name>\n");
		buffer.append("         <url>");
		buffer.append("a");
		buffer.append("</url>\n");
		buffer.append("      </repository>\n");
		buffer.append("   </distributionManagement>\n");
		final List dependencies = pom.getDependencyManagement()
				.getDependencies();
		if (!dependencies.isEmpty()) {
			buffer.append("   <dependencies>\n");
			final Iterator iterator = dependencies.iterator();
			while (iterator.hasNext()) {
				final Dependency dependency = (Dependency) iterator.next();
				final String groupId = dependency.getGroupId();
				final String artifactId = dependency.getArtifactId();
				String version = dependency.getVersion();
				final String scope = dependency.getScope();
				String type = dependency.getType();
				if (!SCOPE_SYSTEM.equals(scope)) {
					final Pom dependentPOM = (Pom) artifacts.get(artifactId);
					if (dependentPOM == null) {
						getLog().error(
								"The plug-in '" + pom.getArtifactId()
										+ "' requires '" + artifactId
										+ "' which was not found");
						continue;
					}
					if (version == null) {
						version = dependentPOM.getVersion();
					}
					if (!version.equals(dependentPOM.getVersion())) {
						version = dependentPOM.getVersion();
					}
					type = dependentPOM.getPackaging();
				}
				buffer.append("      <dependency>\n");
				buffer.append("         <groupId>");
				buffer.append(groupId);
				buffer.append("</groupId>\n");
				buffer.append("         <artifactId>");
				buffer.append(artifactId);
				buffer.append("</artifactId>\n");
				buffer.append("         <version>");
				buffer.append(version);
				buffer.append("</version>\n");
				if (type != null) {
					buffer.append("         <type>");
					buffer.append(type);
					buffer.append("</type>\n");
				}
				buffer.append("      </dependency>\n");
			}
			buffer.append("   </dependencies>\n");
		}
		buffer.append("</project>\n");
		final File file = pom.getPomFile();
		final FileWriter writer = new FileWriter(file);
		writer.write(buffer.toString());
		writer.close();
	}

	private void installPOMCommands(final Pom pom) throws IOException,
			ArtifactInstallationException {
		final StringBuffer buffer = new StringBuffer();
		final Iterator<?> iterator = pom.getDependencyManagement()
				.getDependencies().iterator();
		final DefaultArtifactRepository repository = new DefaultArtifactRepository(
				"a", "a", new DefaultRepositoryLayout());

		getLog()
				.info(
						"working on: " + pom.getGroupId() + " - "
								+ pom.getArtifactId());
		getLog().debug("copy dependencies...");
		// install the dependencies
		while (iterator.hasNext()) {
			final Dependency dependency = (Dependency) iterator.next();
			final String scope = dependency.getScope();

			if (SCOPE_SYSTEM.equals(scope)) {
				final String groupId = dependency.getGroupId();
				final String artifactId = dependency.getArtifactId();
				final String version = dependency.getVersion();

				final File file = new File(repositoryHome, artifactId + "-"
						+ version + EXTENSION_JAR);
				final ArtifactHandler handler = new DefaultArtifactHandler(
						dependency.getType());
				final Artifact artifact = new DefaultArtifact(groupId,
						artifactId, VersionRange.createFromVersion(version),
						scope, dependency.getType(), "", handler);
				installer.install(file, artifact, localRepository);
			}
		}
		// install the actual pom

		final String groupId = pom.getGroupId();
		final String artifactId = pom.getArtifactId();
		final String version = pom.getVersion();

		if (PACKAGING_JAR.equals(pom.getPackaging())) {
			getLog().debug("copy our jar...");
			// deploy-file command line for plug-in jar:

			try {
				final File file = new File(repositoryHome, artifactId + "-"
						+ version + EXTENSION_JAR);
				final ArtifactHandler handler = new DefaultArtifactHandler(pom
						.getPackaging());
				final Artifact artifact = new DefaultArtifact(groupId,
						artifactId, VersionRange.createFromVersion(version),
						null, pom.getPackaging(), null, handler);
				installer.install(file, artifact, localRepository);
			} catch (final Exception e) {
				getLog().error(e);
			}
		} else {
			getLog().debug("copy our pom...");

			final File file = new File(repositoryHome, artifactId + "-"
					+ version + EXTENSION_POM);
			final ArtifactHandler handler = new DefaultArtifactHandler(pom
					.getPackaging());
			final Artifact artifact = new DefaultArtifact(groupId, artifactId,
					VersionRange.createFromVersion(version), null, pom
							.getPackaging(), null, handler);
			installer.install(file, artifact, localRepository);
		}

	}

}
